﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using GrayParrot.Caching;
using PostSharp.Aspects;
using PostSharp.Aspects.Dependencies;
using PostSharp.Extensibility;

namespace GrayParrot.Infrastructure
{
    [Serializable]
    //[AspectTypeDependency(AspectDependencyAction.Order, AspectDependencyPosition.After, typeof(WithExceptionHandling))]
    public class WithCache : MethodInterceptionAspect
    {
        [NonSerialized]
        private static readonly ICache m_cache = null;
        private string m_methodName;
        private string m_className;
        [NonSerialized]
        private object syncRoot;

        static WithCache()
        {
            if (!PostSharpEnvironment.IsPostSharpRunning)
            {
                m_cache = CacheHelper.Instance.GetCache();
            }
        }

        public override void RuntimeInitialize(MethodBase method)
        {
            syncRoot = new object();
        }

        public override void CompileTimeInitialize(MethodBase method, AspectInfo aspectInfo)
        {
            m_methodName = method.Name;
            m_className = method.DeclaringType.Name;
        }

        public override bool CompileTimeValidate(MethodBase method)
        {
            var methodInfo = method as MethodInfo;
            if (methodInfo != null)
            {
                var returnType = methodInfo.ReturnType;
                if (IsDisallowedCacheReturnType(returnType))
                {
                    Message.Write(SeverityType.Error, "998",
                      "Methods with return type {0} cannot be cached in {1}.{2}",
                      returnType.Name, m_className, m_methodName);
                    return false;
                }
            }
            return true;
        }

        public override void OnInvoke(MethodInterceptionArgs args)
        {
            var key = BuildCacheKey(args.Arguments);

            if (m_cache.Get<object>(key) != null)
            {
                args.ReturnValue = m_cache.Get<object>(key);
            }
            else
            {
                lock (syncRoot)
                {
                    if (m_cache.Get<object>(key) == null)
                    {
                        var returnVal = args.Invoke(args.Arguments);
                        args.ReturnValue = returnVal;
                        m_cache.Add<object>(key, returnVal);
                    }
                    else
                    {
                        args.ReturnValue = m_cache.Get<object>(key);
                    }
                }
            }
        }

        private string BuildCacheKey(Arguments arguments)
        {
            var sb = new StringBuilder();
            sb.Append(m_methodName);
            foreach (var argument in arguments.ToArray())
            {
                sb.Append(argument == null ? "_" : argument.ToString());
            }
            return sb.ToString();
        }

        private static readonly List<Type> DisallowedTypes = new List<Type>()
        {
            typeof (IDisposable),
            typeof (Stream),
            typeof (IEnumerable),
            typeof (IQueryable),
            typeof (void)
        };

        private static bool IsDisallowedCacheReturnType(Type returnType)
        {
            return DisallowedTypes.Any(t => t.IsAssignableFrom(returnType));
        }
    }
}
